package scaladuct

import actors.Actor
import hint.HintsHelper
import java.lang.String
import org.apache.commons.convert.{Converter, Converters}
import collection.immutable.List
import java.util.{ArrayList, Arrays, Collections, ResourceBundle}
import util.ColUtils._

/**
 * <p>
 * Created by IntelliJ IDEA.
 * User: zslajchrt
 * Date: Aug 26, 2010
 * Time: 8:38:32 PM
 */

trait CookieClient[X] {
  type BreakContext >: Null

  def edit(pageTO: ConfigPageTO, cookie: AnyRef, breakContext: BreakContext): MessageFromClient = {
    if (cookie == Terminal)
      EditedCookie(Terminal, pageTO, null)
    else {
      val cookieBean = CookieBeanFactory(cookie)
      moderate(pageTO, cookieBean, breakContext)
    }
  }

  protected def moderate(pageTO: ConfigPageTO, cookieBean: CookieBean[AnyRef], breakContext: BreakContext): MessageFromClient

  protected def handleResult(starter: Starter[X])

  protected def handleNoResult(): Nothing

  protected def handleAnswer(starter: Starter[_])

  protected def handleNoAnswer(question: AnyRef)

}

trait CookieClientActor[X] extends CookieClient[X] with Actor {
  val server: Actor

  def act() {
    react {
      case EditCookie(pageTO, cookie, context) => {
        server ! edit(pageTO, cookie, null)
      }
      case ResultStarter(starter, context) => {
        handleResult(starter.asInstanceOf[Starter[X]])
      }
      case NoResult(context) => {
        handleNoResult()
      }
      case AnswerStarter(starter, question, origPageTO, origCookie, context) => {
        handleAnswer(starter)
        server ! edit(origPageTO, origCookie, question.breakContext.asInstanceOf[BreakContext])
      }
      case NoAnswer(question, origPageTO, origCookie, context) => {
        handleNoAnswer(question)
        server ! edit(origPageTO, origCookie, question.breakContext.asInstanceOf[BreakContext])
      }
    }
  }
}

trait CookieClientModerator[X] extends CookieClient[X] with ServerMessageProcessor {
  def processServerMessage(serverMessage: MessageFromServer): Option[MessageFromClient] = serverMessage match {
    case EditCookie(pageTO, cookie, context) => {
      Some(edit(pageTO, cookie, null))
    }
    case ResultStarter(starter, context) => {
      handleResult(starter.asInstanceOf[Starter[X]])
      None
    }
    case NoResult(context) => {
      handleNoResult()
    }
    case AnswerStarter(starter, question, origPageTO, origCookie, context) => {
      handleAnswer(starter)
      Some(edit(origPageTO, origCookie, question.breakContext.asInstanceOf[BreakContext]))
    }
    case NoAnswer(question, origPageTO, origCookie, context) => {
      handleNoAnswer(question)
      Some(edit(origPageTO, origCookie, question.breakContext.asInstanceOf[BreakContext]))
    }
  }
}

class LoopbackClient[X] extends CookieClientModerator[X] {
  private[this] var res: Starter[X] = null
  def result: Starter[X] = res

  protected def handleNoAnswer(question: AnyRef) = null

  protected def handleAnswer(starter: Starter[_]) = null

  protected def handleNoResult() = throw new IllegalStateException

  protected def handleResult(starter: Starter[X]) { res = starter }

  protected def moderate(pageTO: ConfigPageTO, cookieBean: CookieBean[AnyRef], breakContext: BreakContext): MessageFromClient = {
    EditedCookie(cookieBean.cookie, pageTO, breakContext)
  }

  type BreakContext = Null
}

class CLBreakContext(val editedProps: Map[String, PropertyValue[AnyRef]])
object CLBreakContext {
  def apply(editedPropsList: List[PropertyValue[AnyRef]]): CLBreakContext = {
    new CLBreakContext(Map(editedPropsList.map(p => (p.name, p)): _*))
  }
}

//class PropertyValueBundle(val masterBundle: ResourceBundle, val prefix: String) extends ResourceBundle {
//  val getKeys = {
//    masterBundle.getKeys.asScalaList.map(_.substring(prefix.length + 1)).asJavaEnumeration
//  }
//
//  def handleGetObject(key: String) = null
//}

abstract class CommandLineClient[X] extends CookieClient[X] {
  type BreakContext = CLBreakContext

  val failFast: Boolean

  private class CommandSignal(val command: MessageFromClient) extends Exception

  protected def moderate(pageTO: ConfigPageTO, cookieBean: CookieBean[AnyRef], breakContext: BreakContext): MessageFromClient = {
    try {

      val origProps: List[PropertyValue[AnyRef]] = cookieBean.props

      if (breakContext == null) {
        processHeader(pageTO, cookieBean)
      }

      val updatedProps = update(pageTO, origProps, List(), breakContext)
      val updatedCookieBean = cookieBean.update(updatedProps)

      if (breakContext == null) {
        processFooter(pageTO, cookieBean)
      }

      EditedCookie(updatedCookieBean.cookie, pageTO, null)

    }
    catch {
      case qs: CommandSignal => qs.command
    }
  }

  private def update(pageTO: ConfigPageTO,
                     notEditedProps: List[PropertyValue[AnyRef]],
                     editedProps: List[PropertyValue[AnyRef]],
                     breakContext: BreakContext): List[PropertyValue[AnyRef]] = {
    notEditedProps match {
      case List() => editedProps
      case prop :: remaining => {
        val updated: PropertyValue[AnyRef] = if (breakContext == null || !breakContext.editedProps.contains(prop.name))
          updateProperty(pageTO, prop, editedProps)
        else
          breakContext.editedProps(prop.name)

        update(pageTO, remaining, updated :: editedProps, breakContext)
      }
    }
  }

  private def updateProperty(pageTO: ConfigPageTO,
                             prop: PropertyValue[AnyRef],
                             editedProps: List[PropertyValue[AnyRef]]): PropertyValue[AnyRef] = {
    def processCommand(response: String): Unit = {
      resolveCommand(pageTO, response, prop, editedProps) match {
        case None =>
        case Some(command) => throw new CommandSignal(command)
      }
    }
    if (prop.refTypeInfo == null) { // do not allow entering values for a reference properties
      try {
        //if (prop.clazz != classOf[List[_]]) {
        if (!classOf[List[_]].isAssignableFrom(prop.clazz)) {
          val response = collectNewValue(pageTO, prop)
          if (response != "") {

            processCommand(response)

            val converter = Converters.getConverter(classOf[String], prop.clazz);
            val converted: AnyRef = converter.convert(response).asInstanceOf[AnyRef]
            val updated: PropertyValue[AnyRef] = prop.update(converted);
            updated
          } else
            prop

        } else {
          // process the list
          val options: List[AnyRef] = prop.value.asInstanceOf[List[AnyRef]]
          val response = collectSelectionFromList(pageTO, prop.asInstanceOf[PropertyValue[List[AnyRef]]])
          if (response != "") {

            processCommand(response)

            val selectedIndex = Integer.parseInt(response)
            val updated: PropertyValue[AnyRef] =
            prop.update(List(options(selectedIndex))).asInstanceOf[PropertyValue[AnyRef]]
            
            updated
          } else
            prop

        }
      } catch {
        case cmd: CommandSignal => throw cmd
        case e: Exception if (!failFast) => {
          printErrorMessage(e)
          updateProperty(pageTO, prop, editedProps)
        }
      }

    } else prop
  }

  protected def resolveCommand(pageTO: ConfigPageTO,
                               enteredValue: String,
                               prop: PropertyValue[AnyRef],
                               editedProps: List[PropertyValue[AnyRef]]): Option[MessageFromClient] = {

    def getSubject(level: Int): String = {
      level match {
        case 0 => prop.name
        case 1 => pageTO.pageId
        case _ => null
      }
    }

    resolveQuestion(enteredValue) match {
      case Some(level) => Some(Question(level, getSubject(level), CLBreakContext(editedProps)))
      case None => {
        if (resolveEscape(enteredValue))
          Some(Escape(null))
        else
          None
      }
    }

  }

  protected def resolveQuestion(enteredValue: String): Option[Int]

  protected def resolveEscape(enteredValue: String): Boolean

  protected def collectNewValue(pageTO: ConfigPageTO, prop: PropertyValue[AnyRef]): String

  protected def collectSelectionFromList(pageTO: ConfigPageTO, prop: PropertyValue[List[AnyRef]]): String

  protected def processHeader(pageTO: ConfigPageTO, cookieBean: CookieBean[AnyRef]) {}

  protected def processFooter(pageTO: ConfigPageTO, cookieBean: CookieBean[AnyRef]) {}

  protected def printErrorMessage(error: Exception)
}


class ConsoleClient[X](noCorrections: Boolean) extends CommandLineClient[X] {
  val failFast = noCorrections

  def this() = this (false)

  private def convertToString(valueClass: Class[AnyRef], value: AnyRef): String = {
    if (HintsHelper.isHint(value)) {
      HintsHelper.hintId(value)
    } else
      try {
        val converterToString: Converter[AnyRef, String] =
        Converters.getConverter(valueClass, classOf[String])
        converterToString.convert(value)
      } catch {
        case e: Exception => "" + value
      }
  }

  protected def collectNewValue(pageTO: ConfigPageTO, prop: PropertyValue[AnyRef]): String = {
    val presetValue = convertToString(prop.clazz.asInstanceOf[Class[AnyRef]], prop.value)
    val prompt: String = prop.name + "(" + prop.clazz.getName + ") [" + presetValue + "]>"
    print(prompt)
    readLine
  }

  protected def collectSelectionFromList(pageTO: ConfigPageTO, prop: PropertyValue[List[AnyRef]]): String = {
    val options = prop.value
    val prompt: String = prop.name + ":"
    println(prompt)
    printOptions(pageTO, prop, options, 0)
    print(">")
    readLine
  }

  private def printOptions(pageTO: ConfigPageTO, prop: PropertyValue[List[AnyRef]], options: List[AnyRef], index: Int) {
    options match {
      case List() =>
      case head :: tail => {
        val optionLabel = convertToString(head.getClass.asInstanceOf[Class[AnyRef]], head)
        println(index + ": " + optionLabel)
        printOptions(pageTO, prop, tail, index + 1)
      }
    }
  }

  protected def resolveEscape(enteredValue: String): Boolean = false

  protected def resolveQuestion(enteredValue: String): Option[Int] = None

  protected def handleResult(starter: Starter[X]) {
    processInstance(starter) {
      instance => println(instance)
    }
  }

  protected def handleAnswer(starter: Starter[_]) {
    processInstance(starter) {
      instance => println(instance)
    }
  }

  protected def handleNoAnswer(question: AnyRef) {
    println("No answer for question '" + question + "'")
  }

  protected def handleNoResult() = {
    println("No result")
    exit
  }

  override protected def processHeader(pageTO: ConfigPageTO, cookieBean: CookieBean[AnyRef]) {
    def printPage(pageTO: ConfigPageTO) {
      print("/" + pageTO.pageId)
    }

    def printPagePath(pageTO: ConfigPageTO) {
      if (pageTO != null) {
        printPagePath(pageTO.parentPage)
        printPage(pageTO)
      }
    }

    printPagePath(pageTO)
    println

  }

  override protected def processFooter(pageTO: ConfigPageTO, cookieBean: CookieBean[AnyRef]) {
    //println("***")
  }

  protected def printErrorMessage(error: Exception) {
    println("" + error)
  }

}

class ConsoleClientModerator[X] extends ConsoleClient[X] with CookieClientModerator[X];